iT邦幫忙

2024 iThome 鐵人賽

DAY 25
0
生成式 AI

生成式AI輔助電子遊戲製作系列 第 25

Day 25 /修好UI的顯示

  • 分享至 

  • xImage
  •  

在昨天的文章中我們看到遊戲畫面中玩家的血條UI並沒有同步運作,這裡我們可以先檢查一下UI的code有沒有問題,之前適用GameManager來修改UI。詳細的code可以去看D14的文章,我們可以發現我們有在GameManagerplayer的資料結構,但我們後來又把playerPlayerController中生成了一次,並且我們的技能是依據PlayerController來寫的。

重作Player

這裡我們可以問一下ChatGPT要怎麼修改,我這裡是想用單例模式(Singleton)來實作角色,然後我的想法是可能可以把單例模式直接寫在Character的class裡,但我不確定這是不是可行的。

我想在其他code也存取到這個角色的數值,但不希望重複實例化。
那我要在這個code的player做單例模式,還是直接在Character的變數裡加入單例模式讓角色不會被重複註冊

要解決這個問題,可以使用單例模式來確保 Character 只會被實例化一次,並且可以在其他腳本中存取相同的角色數據。你有兩種選擇:

  1. PlayerController 中實現單例模式,讓 PlayerController 中的 player 成為全局的唯一實例。
  2. Character 類中實現單例模式,確保不會重複創建 Character 實例,並且能夠從任何地方存取。

我會為這兩種方式各自提供解決方案,並討論哪種方法更合適。

方式 1:在 PlayerController 中實現單例模式

你可以將 PlayerController 設置為單例,這樣其他代碼可以直接通過 PlayerController.Instance.player 來存取玩家的數據。

修改 PlayerController 為單例模式:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    // 單例
    public static PlayerController Instance { get; private set; }

    // 定義一個角色,這裡可以是玩家角色
    public Character player;

    // 定義技能
    private ISkill flameImpact;
    private ISkill healingLight;

    void Awake()
    {
        // 檢查是否已有實例存在,若有,則銷毀重複的實例
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
        }
        else
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);  // 遊戲場景切換時保持單例存在
        }
    }

    void Start()
    {
        // 初始化玩家
        player = new Character("Player", 100, 50);

        // 初始化技能,這裡引用之前的技能實現
        flameImpact = new FlameImpact();
        healingLight = new HealingLight();
    }

    void Update()
    {
    }

    public void PlayerUseHealingLight()
    {
        healingLight.UseSkill(player);
        Debug.Log("玩家魔力: " + player.Mana);
    }

    public void PlayerUseFlameImpact()
    {
        flameImpact.UseSkill(player);
    }
}

如何在其他腳本中使用 PlayerController 單例來存取玩家數據:

public class GameManager : MonoBehaviour
{
    void Start()
    {
        // 直接通過PlayerController的單例來存取玩家數據
        PlayerController.Instance.PlayerUseHealingLight();  // 讓玩家使用治癒技能
        Debug.Log("玩家生命值: " + PlayerController.Instance.player.Health);
    }
}

這樣,PlayerController 成為一個全局唯一的實例,可以在其他任何腳本中通過 PlayerController.Instance 存取玩家數據,而不需要重複實例化。

方式 2:在 Character 類中實現單例模式

如果你希望 Character 本身只存在一個實例,而不依賴於 PlayerController,你可以在 Character 類中實現單例模式。

修改 Character 類為單例模式:

public class Character
{
    // 單例
    private static Character _instance;
    public static Character Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new Character("Player", 100, 50);
            }
            return _instance;
        }
    }

    public string Name { get; private set; }
    public float Health { get; private set; }
    public float Mana { get; private set; }

    // 私有構造函數,防止外部直接實例化
    private Character(string name, float health, float mana)
    {
        Name = name;
        Health = health;
        Mana = mana;
    }

    public void TakeDamage(float damage)
    {
        Health -= damage;
        if (Health < 0) Health = 0;
    }

    public void Heal(float healAmount)
    {
        Health += healAmount;
        if (Health > 100) Health = 100;
    }

    public void UseMana(float manaCost)
    {
        Mana -= manaCost;
        if (Mana < 0) Mana = 0;
    }
}

如何在 PlayerController 和其他腳本中使用單例 Character

PlayerController 中,不再需要實例化 Character,可以直接使用 Character.Instance

public class PlayerController : MonoBehaviour
{
    // 定義技能
    private ISkill flameImpact;
    private ISkill healingLight;

    void Start()
    {
        // 初始化技能
        flameImpact = new FlameImpact();
        healingLight = new HealingLight();
    }

    void Update()
    {
    }

    public void PlayerUseHealingLight()
    {
        healingLight.UseSkill(Character.Instance);
        Debug.Log("玩家魔力: " + Character.Instance.Mana);
    }

    public void PlayerUseFlameImpact()
    {
        flameImpact.UseSkill(Character.Instance);
    }
}

在其他腳本中,你可以像這樣使用 Character.Instance 來存取玩家數據:

public class GameManager : MonoBehaviour
{
    void Start()
    {
        // 通過Character的單例來存取玩家數據
        Debug.Log("玩家初始生命值: " + Character.Instance.Health);

        // 讓玩家使用治癒技能
        PlayerController.Instance.PlayerUseHealingLight();
    }
}

選擇哪種方式?

  • 方式 1:在 PlayerController 中實現單例模式

    • 適合需要更多控制和管理玩家行為的情況。
    • PlayerController 可以不僅控制 Character,還可以控制技能、行為邏輯等其他組件。
  • 方式 2:在 Character 類中實現單例模式

    • 適合單純需要確保玩家數據唯一的情況,而不涉及複雜的行為邏輯。
    • 如果你只需要一個全局的玩家數據實例,並且不需要將其與行為邏輯捆綁,可以使用這種方式。

總結

  • 如果你需要 統一管理角色行為和數據,推薦使用方式 1,讓 PlayerController 成為單例。
  • 如果你只需要保證角色數據唯一性,並且需要在多個腳本中訪問,方式 2Character 本身成為單例會更直接。

根據你的需求選擇合適的方式來實現單例模式。


根據ChatGPT的回答,比較適合我們的方法應該是方法1,所以我們將PlayerController的code更新成ChatGPT寫的,還需要修改一下GameManger的code。

GameManger的code

using UnityEngine;
using UnityEngine.UI;



public class GameManager : MonoBehaviour
{
    // 玩家和AI的角色實例
    //public Skill.Character player;
    //public Skill.Character ai;

    // 連接到UI元件的引用
    public Slider playerHealthSlider;
    public Slider playerManaSlider;
    public Slider aiHealthSlider;
    public Slider aiManaSlider;

    // 初始化玩家和AI的生命值與法力值
    void Start()
    {
        //// 初始化玩家和AI角色
        //player = new Skill.Character("Player", 100, 50);
        //ai = new Skill.Character("AI", 100, 50);

        UpdateUI(); // 初始化UI
    }

    void Update()
    {
        UpdateUI(); // 實時更新UI

        //if(Input.GetKeyUp(KeyCode.Space)) 
        //{
        //    Debug.Log("玩家生命"+player.Health);
        //    Debug.Log("玩家魔力" + player.Mana);
        //}
    }

    // 更新UI顯示玩家和AI的生命值、法力值
    void UpdateUI()
    {
        playerHealthSlider.value = PlayerController.Instance.player.Health / 100;
        playerManaSlider.value = PlayerController.Instance.player.Mana / 100;
        //aiHealthSlider.value = ai.Health / 100;
        //aiManaSlider.value = ai.Mana / 100;
    }


}

這裡我先修改了player的Health跟Mana的更新,我們再到Unity裡執行看看。
https://ithelp.ithome.com.tw/upload/images/20241009/20169512k0yOsKgI48.pnghttps://ithelp.ithome.com.tw/upload/images/20241009/20169512k0XvdzFTJc.png

player沒有正確實例化

UI是有正常運作了,但出現了一個錯誤訊息。這裡的錯誤大概是指我們沒有把player先實例化出來,雖然看起來不影響遊戲的進行,我們還是可以試著修一下,我們直接把錯誤訊息丟給ChatGPT看看。

NullReferenceException: Object reference not set to an instance of an object
GameManager.UpdateUI () (at Assets/Script/GameManger.cs:42)
GameManager.Start () (at Assets/Script/GameManger.cs:25)

NullReferenceException 表示你在程式中嘗試存取一個尚未初始化的物件。根據你的錯誤信息,錯誤發生在 GameManager.UpdateUI() 的第42行和 GameManager.Start() 的第25行。這通常是因為你嘗試使用了一個尚未設置的變數或物件。

具體的原因可能有幾個,常見的原因是你的 Slider 元件尚未在 Inspector 中正確連接。讓我們來檢查解決這個問題的步驟。

檢查解決步驟:

  1. 檢查 Slider 是否正確連接到 Inspector 中的變數:

    • 在 Unity 編輯器中,選中掛有 GameManager 腳本的 GameObject(應該是場景中的某個物件)。
    • 在 Inspector 中,你應該能夠看到 playerHealthSliderplayerManaSlideraiHealthSlideraiManaSlider 這些變數的字段。
    • 確保你將對應的 UI Slider 元件拖到這些字段中。這些 Slider 應該是你場景中的 UI 元素(例如,顯示生命值和法力值的條形圖)。
  2. 確認 UI 元件是否存在於場景中:

    • 確保你已經在場景中放置了對應的 UI 元件(如 Slider),並且它們是活躍的(即它們的 GameObject 沒有被禁用)。
  3. 檢查 UpdateUI 函數:

    • 檢查 UpdateUI 函數中是否正確存取了 Slider 的屬性。例如,應該確保 playerHealthSlider.value 這樣的表達式在 Slider 存在的前提下執行。

例子:GameManager.cs 的簡化版本

using UnityEngine;
using UnityEngine.UI;

public class GameManager : MonoBehaviour
{
    // 玩家和AI的角色實例
    public Character player;
    public Character ai;

    // UI 元件的引用
    public Slider playerHealthSlider;
    public Slider playerManaSlider;
    public Slider aiHealthSlider;
    public Slider aiManaSlider;

    void Start()
    {
        // 確保 UI 元件已正確連接
        if (playerHealthSlider == null || playerManaSlider == null || aiHealthSlider == null || aiManaSlider == null)
        {
            Debug.LogError("UI Slider 元件未正確設置,請檢查 Inspector 中的設置。");
            return;
        }

        // 初始化玩家和 AI
        player = new Character("Player", 100, 50);
        ai = new Character("AI", 100, 50);

        // 更新 UI
        UpdateUI();
    }

    void UpdateUI()
    {
        // 確保 Slider 元件存在後再操作
        if (playerHealthSlider != null && playerManaSlider != null && aiHealthSlider != null && aiManaSlider != null)
        {
            playerHealthSlider.value = player.Health / 100;
            playerManaSlider.value = player.Mana / 100;
            aiHealthSlider.value = ai.Health / 100;
            aiManaSlider.value = ai.Mana / 100;
        }
    }
}

測試步驟:

  1. 在 Unity 編輯器中,確認你的 Slider 元件是否正確連接到 GameManager 的對應字段。
  2. 如果你在場景中有多個場景物件或者 Slider,確保你所連接的 Slider 是對應的那一個。
  3. 確保在 Start()UpdateUI() 執行之前,所有的 Slider 變數都已經被初始化,否則會拋出 NullReferenceException

注意:

  • 如果你的 Slider 是在運行時動態創建的,則需要在創建後設置它們的引用。如果它們是靜態 UI 元件,則確保已經在 Inspector 中設置正確。

通過這些步驟應該能夠解決 NullReferenceException 錯誤。


我們可以看到ChatGPT是直接修改GameManager的code,但我們其實已經在PlayerController中的 void Start()實例化player。所以正確方式應該是再提早player的實例化,我們把player的實例化移到void Awake()測試看看。
https://ithelp.ithome.com.tw/upload/images/20241009/20169512RKbHo2wX8r.png

看起來是沒問題了,我再使用技能看看。
https://ithelp.ithome.com.tw/upload/images/20241009/20169512kpAZkYhhOG.png

這樣看起來遊戲有正常運作了!


上一篇
Day 24/Unity按鈕案例實作
下一篇
Day 26 /敵人AI在遊戲中的技能策略
系列文
生成式AI輔助電子遊戲製作30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言